﻿//--------------------------------------------------------------------------------
// This file is part of the MusicWho - MusicComposer.
//
// Copyright © 2010 Alex-Joyce. All rights reserved.
//
// For information about this application and licensing, go to http://musicwho.codeplex.com.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
//--------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace MWCore
{
    public interface IComposer
    {
    }

    public interface IPlayer
    {
    }


    public static class MIDI
    {
        static List<int> _notetimedivision=new List<int>();
        static byte _notedotmax = 3;

        public const string HEADER = "MThd";
        public const int HEADERSIZE = 6;
        public const string TRACK = "MTrk";
        public const short TIMEDIVISION4TH = 256;
        public const int MICROSECONDSPERMINUTE = 60000000;

        #region Midi Event Type
        public const byte PROGRAMCHANGE = 12;
        public const byte NOTEON = 9;
        public const byte NOTEOFF = 8;
        public const byte NOTEAFTERTOUCH = 10;
        public const byte CHANNELAFTERTOUCH = 13;
        public const byte PITCHBEND = 14;
        public const byte CONTROLLER = 11;
        #endregion

        #region Controller Type
        public const byte BANKSELECT = 0;
        public const byte MODULATION = 1;
        public const byte BREATH = 2;
        public const byte FOOT = 4;
        public const byte PROTAMENTOTIME = 5;
        public const byte DATAENTRY = 6;
        public const byte MAINVOLUME = 7;
        public const byte BALANCE = 8;
        public const byte PAN = 10;
        public const byte EXPRESSION = 11;
        public const byte EFFECT1 = 12;
        public const byte EFFECT2 = 13;
        public const byte DAMPERPEDAL = 64;
        public const byte PROTAMENTO = 65;
        public const byte SOSTENUTO = 66;
        public const byte SOFTPEDAL = 67;
        public const byte LEGATOFOOTSWITCH = 68;
        public const byte HOLD2 = 69;
        public const byte PROTAMENTOCONTROL = 84;
        public const byte DATAINCREMENT = 96;
        public const byte DATADECREMENT = 97;
        #endregion

        #region Program Instrument
        #region Piano
        public const byte ACOUSTICGRANDPIANO = 0;
        public const byte BRIGHTACOUSTICPIANO = 1;
        public const byte ELECTRICGRANDPIANO = 2;
        public const byte HONKYTONKPIANO = 3;
        public const byte ELECTRICPIANO1 = 4;
        public const byte ELECTRICPIANO2 = 5;
        public const byte HARPSICHORD = 6;
        public const byte CLAVINET = 7;
        #endregion
        #region Chromatic Percussion
        public const byte CELESTA = 8;
        public const byte GLOCKENSPIEL = 9;
        public const byte MUSICBOX = 10;
        public const byte VIBRAPHONE = 11;
        public const byte MARIMBA = 12;
        public const byte XYLOPHONE = 13;
        public const byte TUBULARBELLS = 14;
        public const byte DULCIMER = 15;
        #endregion
        #region Organ
        public const byte DRAWBARORGAN = 16;
        public const byte PERCUSSIVEORGAN = 17;
        public const byte ROCKORGAN = 18;
        public const byte CHURCHORGAN = 19;
        public const byte REEDORGAN = 20;
        public const byte ACCORDION = 21;
        public const byte HARMONICA = 22;
        public const byte TANGOACCORDION = 23;
        #endregion
        #region Guitar
        public const byte ACOUSTICGUITARNYLON = 24;
        public const byte ACOUSTICGUITARSTEEL = 25;
        public const byte ELECTRICGUITARJAZZ = 26;
        public const byte ELECTRICGUITARCLEAN = 27;
        public const byte ELECTRICGUITARMUTED = 28;
        public const byte OVERDRIVENGUITAR = 29;
        public const byte DISTORTIONGUITAR = 30;
        public const byte GUITARHARMONICS = 31;
        #endregion
        #region Bass
        public const byte ACOUSTICBASS = 32;
        public const byte ELECTRICBASSFINGER = 33;
        public const byte ELECTRICBASSPICK = 34;
        public const byte FRETLESSBASS = 35;
        public const byte SLAPBASS1 = 36;
        public const byte SLAPBASS2 = 37;
        public const byte SYNTHBASS1 = 38;
        public const byte SYNTHBASS2 = 39;
        #endregion
        #region Strings
        public const byte VIOLIN = 40;
        public const byte VIOLA = 41;
        public const byte CELLO = 42;
        public const byte CONTRABASS = 43;
        public const byte TREMOLOSTRINGS = 44;
        public const byte PIZZICATOSTRINGS = 45;
        public const byte ORCHESTRALHARP = 46;
        public const byte TIMPANI = 47;
        #endregion
        #region Brass
        public const byte TRUMPET = 56;
        public const byte TROMBONE = 57;
        public const byte TUBA = 58;
        public const byte MUTEDTRUMPET = 59;
        public const byte FRENCHHORN = 60;
        public const byte BRASSSECTION = 61;
        public const byte SYNTHBRASS1 = 62;
        public const byte SYNTHBRASS2 = 63;
        #endregion
        #endregion

        #region Meta Event Type
        public const byte METAEVENT = 255;
        public const byte SEQUENCENUMBER = 0;
        public const byte COPYRIGHT = 2;
        public const byte TRACKNAME = 3;
        public const byte ENDOFTRACK = 47;
        public const byte TEMPO = 81;
        public const byte KEYSIGNATURE = 89;
        public const byte TEXT = 1;
        public const byte TIMESIGNATURE = 88;
        #endregion


        public static bool IsLittleEndian
        {
            get { return BitConverter.IsLittleEndian; }
        }

        public static T[] Reverse<T>(T[] source)
        {
            int n = source.Length;
            int half = (int)Math.Floor(n / 2.0);
            for (int i = 0; i < half; i++)
            {
                T t = source[i];
                source[i] = source[n - 1 - i];
                source[n - 1 - i] = t;
            }
            return source;
        }

        public static byte[] Reverse(byte[] source)
        {
            return Reverse<byte>(source);
        }

        public static string Reverse(string source)
        {
            var t = Reverse<char>(source.ToCharArray());
            StringBuilder sb = new StringBuilder();
            sb.Append(t);
            return sb.ToString();
        }

        public static string EndianPad(string source, bool islittleendian,int padn)
        {
            if (islittleendian)
            {
                return source.PadLeft(padn, '0');
            }
            return source.PadRight(padn, '0');
        }

        public static int EndianConvert(int source)
        {
            var t = Convert.ToString(source, 2);
            t =Reverse( EndianPad(t, IsLittleEndian, 32));
            return Convert.ToInt32(t, 2);
        }

        public static byte[] VariableByte(int value)
        {
            if (value < 128)
            {
                return new byte[] { (byte)value };
            }
            if (value < 16384)
            {
                byte[] vb = new byte[2];
                var vs = EndianPad(Convert.ToString(value, 2), IsLittleEndian, 14);
                vb[0] = Convert.ToByte("1" + vs.Substring(0, 7), 2);
                vb[1] = Convert.ToByte("0" + vs.Substring(7, 7), 2);
                return vb;
            }
            if (value < 2097152)
            {
                byte[] vb = new byte[3];
                var vs = EndianPad(Convert.ToString(value, 2), IsLittleEndian, 21);
                vb[0] = Convert.ToByte("1" + vs.Substring(0, 7), 2);
                vb[1] = Convert.ToByte("1" + vs.Substring(7, 7), 2);
                vb[2] = Convert.ToByte("0" + vs.Substring(14, 7), 2);
                return vb;
            }
            else
            {
                byte[] vb = new byte[4];
                var vs = EndianPad(Convert.ToString(value, 2), IsLittleEndian, 28);
                vb[0] = Convert.ToByte("1" + vs.Substring(0, 7), 2);
                vb[1] = Convert.ToByte("1" + vs.Substring(7, 7), 2);
                vb[2] = Convert.ToByte("1" + vs.Substring(14, 7), 2);
                vb[3] = Convert.ToByte("0" + vs.Substring(21, 7), 2);
                return vb;
            }
        }


        public static List<byte> Header(short format,short ntrack,short timedivision)
        {
            List<byte> hd = new List<byte>();
            hd.AddRange(Encoding.ASCII.GetBytes(HEADER));
            hd.AddRange(Reverse(BitConverter.GetBytes(HEADERSIZE)));
            hd.AddRange(Reverse(BitConverter.GetBytes(format)));
            hd.AddRange(Reverse(BitConverter.GetBytes(ntrack)));
            hd.AddRange(TimeDivision(timedivision));
            return hd;
        }

        public static byte[] TimeDivision(short timedivision)
        {
            var t =EndianPad( Convert.ToString(timedivision, 2),IsLittleEndian,16);
            t = "0" + t.Substring(1);
            var tt = Convert.ToInt16(t, 2);
            return Reverse(BitConverter.GetBytes(tt));
        }

        public static List<byte> Track(List<byte> trackcontent)
        {
            List<byte> track = new List<byte>();
            track.AddRange(Encoding.ASCII.GetBytes(TRACK));
            track.AddRange(Reverse(BitConverter.GetBytes(trackcontent.Count)));
            track.AddRange(trackcontent);
            return track;
        }


        #region Midi Event
        
        public static List<byte> MidiEvent(int deltatime,byte type,byte channel,Nullable<byte> param1,Nullable< byte> param2)
        {
            List<byte> me = new List<byte>();
            var btype =EndianPad( Convert.ToString(type, 2),IsLittleEndian,8);
            var bchannel  =EndianPad(Convert.ToString(channel,2),IsLittleEndian,8);
            var t = btype.Substring(4) + bchannel.Substring(4);
            var tt = Convert.ToByte(t, 2);
            me.AddRange(VariableByte(deltatime));
            me.Add(tt);
            if (param1.HasValue)
            {
                me.Add(param1.Value);
            }
            if (param2.HasValue)
            {
                me.Add(param2.Value);
            }
            return me;
        }

        public static List<byte> ProgramChange(int deltatime, byte channel, byte program)
        {
            return MidiEvent(deltatime, PROGRAMCHANGE, channel, program, null);
        }

        public static List<byte> NoteOff(int deltatime, byte channel,byte note,byte velocity)
        {
            return MidiEvent(deltatime, NOTEOFF, channel, note, velocity);
        }

        public static List<byte> NoteOn(int deltatime,byte channel,byte note, byte velocity)
        {
            return MidiEvent(deltatime, NOTEON, channel, note, velocity);
        }

        public static List<byte> NoteAfterTouch(int deltatime,byte channel,byte note, byte pressure)
        {
            return MidiEvent(deltatime, NOTEAFTERTOUCH, channel, note, pressure);
        }

        public static List<byte> ChannelAfterTouch(int deltatime, byte channel,byte pressure)
        {
            return MidiEvent(deltatime, CHANNELAFTERTOUCH, channel, pressure, null);
        }

        public static List<byte> Controller(int deltatime,byte channel,byte controllertype,byte value)
        {
            return MidiEvent(deltatime, CONTROLLER, channel, controllertype, value);
        }

        public static List<byte> PitchBend(int deltatime,byte channel,byte lsb,byte msb)
        {
            return MidiEvent(deltatime, PITCHBEND, channel, lsb, msb);
        }

        #endregion

        #region Meta Event
        
        public static List<byte> MetaEvent(int deltatime,byte type,List< byte> data)
        {
            List<byte> me = new List<byte>();
            me.AddRange(VariableByte(deltatime));
            me.Add(METAEVENT);
            me.Add(type);
            me.AddRange(VariableByte(data.Count));
            me.AddRange(data);
            return me;
        }

        public static List<byte> SequenceNumber(short n)
        {
            List<byte> d = new List<byte>();
            d.AddRange(Reverse( BitConverter.GetBytes(n)));
            return MetaEvent(0, SEQUENCENUMBER, d);
        }

        public static List<byte> Copyright(string text)
        {
            List<byte> t = new List<byte>();
            t.AddRange(Encoding.ASCII.GetBytes(text));
            return MetaEvent(0, COPYRIGHT, t);
        }

        public static List<byte> TrackName(string text)
        {
            List<byte> t = new List<byte>();
            t.AddRange(Encoding.ASCII.GetBytes(text));
            return MetaEvent(0, TRACKNAME, t);
        }

        public static List<byte> EndOfTrack(int deltatime)
        {
            return MetaEvent(deltatime,ENDOFTRACK, new List<byte>());
        }

        public static List<byte> Tempo(int deltatime, int beatsperminute)
        {
            List<byte> uspqnb = new List<byte>();
            int uspqn = MICROSECONDSPERMINUTE / beatsperminute;
            var t = BitConverter.GetBytes(uspqn);
            uspqnb.Add(t[2]);
            uspqnb.Add(t[1]);
            uspqnb.Add(t[0]);
            return MetaEvent(deltatime, TEMPO, uspqnb);
        }

        public static List<byte> KeySignature(int deltatime,sbyte key, byte scale)
        {
            List<byte> ks = new List<byte>();
            ks.Add((byte)key);
            ks.Add(scale);
            return MetaEvent(deltatime, KEYSIGNATURE, ks);
        }

        public static List<byte> Text(int deltatime,string text)
        {
            List<byte> t = new List<byte>();
            t.AddRange(Encoding.ASCII.GetBytes(text));
            return MetaEvent(deltatime, TEXT, t);
        }

        public static List<byte> TimeSignature(int deltatime,byte numerator,byte denominator,byte metronome,byte n32nd)
        {
            List<byte> ts = new List<byte>();
            ts.Add(numerator);
            ts.Add(denominator);
            ts.Add(metronome);
            ts.Add(n32nd);
            return MetaEvent(deltatime, TIMESIGNATURE, ts);
        }

        #endregion


        public static byte[] ScaleTable(sbyte key,byte scale)
        {
            return null;
        }

        public static int InBeats(int deltatime,short timedivision4th=TIMEDIVISION4TH,byte ndivision4thnote=5)
        {
            var notetime = GetNoteTimeDivision;
            if (notetime.Count != ndivision4thnote + 3)
            {
                NoteTimeDivision(timedivision4th, ndivision4thnote, 2);
                notetime = GetNoteTimeDivision;
            }
            notetime.FindLastIndex(new Predicate<int>(x => x < deltatime));
            return 1;
        }

        public static byte NoteDotMax
        {
            get { return _notedotmax; }
            set { _notedotmax = value; }
        }

        public static List<int> GetNoteTimeDivision
        {
            get { return _notetimedivision; }
        }

        public static int[] NoteTimeDivision(short timedivision4th,byte ndivision4thnote,byte ntime4thnote)
        {
            int[] nd = new int[ndivision4thnote+1];
            nd[ndivision4thnote] = timedivision4th;
            for (int i = ndivision4thnote-1; i > -1; i--)
            {
                nd[i]=nd[i+1]/2;
            }
            int[] nt = new int[ntime4thnote + 1];
            nt[0] = timedivision4th;
            for (int i = 1; i < ntime4thnote + 1; i++)
            {
                nt[i] = nt[i - 1] * 2;
            }
            var ntd = nd.Take<int>(nd.Length-1).Concat<int>(nt);
            _notetimedivision = ntd.ToList<int>();
            return ntd.ToArray();
        }


        public static void Save(string filepath,List<byte> music)
        {
            FileStream fs = new FileStream(filepath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
            BinaryWriter bw = new BinaryWriter(fs);
            bw.Write(music.ToArray());
        }
    }
}
